(function($) {
	$.fn.PropertyGrid = function(settings) {
		settings = $.extend({}, settings || {});

		return this.each(function() {

			var currentedit = null;
			var endeditlock = null;

			var _pg = $(this);
			var _id = _pg.attr('id');
			var _cfg = $.extend({
				id: _id,
				items: $.map(
					$('.editor [id^="' + _id + '"]', _pg).get(),
					function(i) {
						var _val = $(i).attr('id');
						var _n = parseInt(_val.slice(_id.length + 1));

						if (isNaN(_n)) {
							_n = parseInt(_val);
						}

						return _n;
					}
				),
				selcolor: '',
				width: _pg.width(),
				bgcolor: '',
				bordercolor: '',
				headerfgcolor: '',
				lineheight: 0,
				padwidth: 18,
				fgcolor: '',
				family: '',
				fontsize: ''
			}, settings);

			var oldval = null;
			var oldlbl = null;

			var LineHeightMargin = function() { return _cfg.lineheight + 1; };
			var HalfWidth = function() { return (_cfg.width - 2 - _cfg.padwidth) / 2; };
			var HalfWidthLess3 = function() { return HalfWidth() - 5; };
			var InputLineHeight = function() { return _cfg.lineheight - 4; };

			var ApplyStyles = function() {
				_pg.css({ 'width': _cfg.width + 'px' });
				$.each({
					'.PG *': { color: _cfg.fgcolor, 'font-family': _cfg.family, 'font-size': _cfg.fontsize },
					'.PGH,.footer,.PGC,.status': { 'border-color:': _cfg.headerfgcolor, 'background-color': _cfg.bgcolor },
					'.PGC *': { 'line-height': _cfg.lineheight + 'px'},
					'.PGI *': { 'height': _cfg.lineheight + 'px' },
					'.PGC_OPEN,.PGC_CLOSED': { 'width': _cfg.padwidth + 'px', 'height': _cfg.lineheight + 'px' },
					'.PGC_HEAD span': { color: _cfg.headerfgcolor },
					'.PGI_NONE,.PGI_OPEN,.PGI_CLOSED': { height: LineHeightMargin() + 'px' },
					'.PGI dt,.editor': { width: HalfWidth() + 'px'},
					'.sub dt span': { 'margin-left': _cfg.padwidth + 'px' },
					'.editor a:hover': { 'background-color': _cfg.selcolor },
					'.editor input': { width: HalfWidthLess3() + 'px', 'line-height': InputLineHeight() + 'px', height: InputLineHeight() + 'px' }
				},
					function(sel, val) {
						$(sel, _pg).css(val);
					}
				);
			};

			var EndEdit = function(sender) {
				if (endeditlock)
					return;

				sender = $(sender);
				endeditlock = sender;
				currentedit = null;
				sender.hide();

				var newval = sender.val();

				var _link = sender.prev().show();
				oldlbl = _link;
				oldlbl.attr('disabled', true);
				oldval = _link.html();
				_link.html(newval).focus();
				UpdateDescription();
			};

			var CancelEdit = function(sender) {
				if (currentedit == null)
					return;

				sender = $(sender);
				currentedit = null;
				endeditlock = sender;

				sender.hide().prev().show();
				UpdateDescription();
			};

			var UpdateDescription = function(result) {

				var _footer = $('.footer', _pg);
				var _oldText = $.data(_footer, 'oldText');
				
				if(result !== undefined && result.hasOwnProperty('length') && result.length == 2) {
					if (_oldText === undefined) {
						$.data(_footer, 'oldText', _footer.html());
					}
					_footer.html('<dl><dt>' + result[0] + '</dt><dd>' + result[1] + '</dd></dl>');
				} else {
					if (_oldText !== undefined) {
						_footer.html(_oldText);
					}
				}
			};

			var toggleCategory = function(pgc, forceOpen) {
				var _btn = pgc.children(':eq(0)');
				var _content = _btn.next().next();

				if (_btn.is('.PGC_OPEN') && 'undefined' == typeof (forceOpen) || forceOpen) {
					_btn.removeClass('PGC_OPEN').addClass('PGC_CLOSED');
					_content.hide('fast');
				} else {
					_btn.addClass('PGC_OPEN').removeClass('PGC_CLOSED');
					_content.show('fast');
				}
			};

			/*Expand/Collapse Category by image*/
			$('.PGC_OPEN', _pg).click(function(e) {
				toggleCategory($(this).parent());
			});

			/*Expand/Collapse Category by hyperlink*/
			$('.PGC_HEAD a', _pg).click(function(e) {
				toggleCategory($(this).parent().parent().parent());
				return false;
			});

			/*Expand All,Collapse All and Help buttons*/
			$('.PGH > A.up', _pg).click(function(e) {
				$('.PGC', _pg).each(function() {
					toggleCategory($(this), false);
				});
			});

			$('.PGH > A.down', _pg).click(function() {
				$('.PGC', _pg).each(function() {
					toggleCategory($(this), true);
				});
			});

			$('.PGH > A.help, .PGH > A.nohelp', _pg).click(function() {
				sender = $(this);
				var ft = $('.footer', _pg);
				var vis = ft.is(':visible');
				sender.toggleClass('help').toggleClass('nohelp');
				vis ? ft.hide('fast') : ft.show('fast');
			});

			var toggleProperty = function(pgi, forceOpen) {
				var _btn = pgi.children(':eq(0)');
				var _content = _btn.parent().next(':first');

				if (_btn.is('.PGI_OPEN') && 'undefined' == typeof (forceOpen) || forceOpen) {
					_btn.removeClass('PGI_OPEN').addClass('PGI_CLOSED');
					_content.hide('fast');
				} else {
					_btn.removeClass('PGI_CLOSED').addClass('PGI_OPEN');
					_content.show('fast');
				}
			};

			/*Expand/collapse property*/
			$('.PGI_OPEN,.PGI_CLOSED', _pg).click(function() {
				toggleProperty($(this).parent());
			});

			/*Update description*/
			$('.PGI > :nth-child(2)', _pg).click(function() {

				if (currentedit)
					EndEdit(currentedit);

				UpdateDescription([$(this).text(), $('dfn', $(this).parent('.PGI')).text()]);
				return false;
			});

			/*begin edit*/
			var _editors = $('.editor select, .editor input, .editor .editor', _pg);
			_editors.each(function() {
				var _editor = $(this).hide();
				
				$('<a title="Click to edit" href="#" />')
					.insertBefore(_editor)
					.text(_editor.val() || '')
					.click(function() {
						if (currentedit) {
							EndEdit(currentedit);
						}

						sender = $(this);
						endeditlock = null;

						sender.hide().parent().prev().css('backgroundColor', _cfg.selcolor);
						currentedit = _editor.val(sender.text()).show().focus();

						return false;
					});
			});

			/*keyboard*/
			/*///TODO: add navigation with UP and DOWN buttons*/
			$('.editor input[type="text"]', _pg).keydown(function(e) {
				if (e.keyCode == 13) {
					EndEdit(this);
					return false;
				}
				if (e.keyCode == 27) {
					CancelEdit(this);
					return false;
				}
				return true;
			}).add('.editor select', _pg).blur(function() {/*//Cancel edit on leave*/
				CancelEdit(this);
			}).change(function() {/*//Store edited values*/
				EndEdit(this);
			});

			$('.PGI dt', _pg).wrapInner('<span/>');

			ApplyStyles();
		});
	};
})(jQuery);